import math
import numpy
import pyaudio
import itertools
import time

import threading, Queue
import sys,os

#from scipy import interpolate
#from operator import itemgetter
 
#chunk = numpy.concatenate(chunks) * 0.25


def enum(*sequential, **named):
    enums = dict(zip(sequential, range(len(sequential))), **named)
    return type('Enum', (), enums)

class soundBuffer(threading.Thread):
    def __init__(self, bufferSize, sndLock):
        threading.Thread.__init__(self)

        self._stop = threading.Event()
        self.sndLock = sndLock                  # A conditional lock so that only one buffer plays at once
        self.buffer       = Queue.Queue(2)      # a queue of buffers to be sent to the audio stream 
        self.bufferLength = bufferSize

    def stop(self):
        print("sound buffer thread request stop")
        self._stop.set()

    def stopped(self):
        return not self.isAlive()                

    def getBufferAvailable(self):
        try:
            self.stream
        except:
            return 0
        else:
            return self.stream.get_write_available()
        
    def bufferEmpty(self):
        return self.buffer.empty()
                      

    def run(self):
        self._stop.clear()
        print("sound buffer thread starting")

        p = pyaudio.PyAudio()
        self.stream = p.open(format=pyaudio.paFloat32, channels=1, rate=44100, output=1, frames_per_buffer = self.bufferLength)


        while(not self._stop.isSet() ):
            try:
                chunk = self.buffer.get(True, 0.2)
            except:
                self._stop.wait(0.01)           # nothing in the buffer so wait for it
            else:
                self.stream.write(chunk.astype(numpy.float32).tostring())     # non returning
                self.buffer.task_done()

                
        self.stream.close()
        p.terminate()

        print("sound buffer thread terminated")


class varioSoundGen(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)

        self._stop = threading.Event()

        self.frequency = 100.00
        self.pulsing = False
        self.outAmplitude = 0.5
        
    
    def stop(self):
        print("sound buffer thread request stop")
        self._stop.set()

    def stopped(self):
        return not self.isAlive()

    def setFrequency(self, frequency):
        self.frequency = frequency

    def setPulsing(self, pulsing):
        self.pulsing = pulsing

    def setAmplitude(self, amplitude):
        if(amplitude > 1.0):
            self.outAmplitude = 0.9
        elif(amplitude < 0.0):
            self.outAmplitude = 0.0
        else:
            self.outAmplitude = amplitude

        
    def run(self):
        print("sound generator thread starting")
        self._stop.clear()

        self.outAmplitude = 0.0
        
        rate = float( 44100.0 )
        period = float( 1/rate )
        angle = float( 0.0 )        # phase angle of output tone
        
        attackTime = 0.05
        decayTime = 0.02
        pulsePeriod = 0.1
        pulseRate = 4.0
        pulseTime = 1/pulseRate
        
        bufferTime = pulseTime #attackTime + decayTime + pulseTime            # Length of sound buffer in seconds
        bufferLength = int( bufferTime * rate )
        
        wave = numpy.arange(bufferLength, dtype=numpy.float32)
        
        amplitude = 0.25
        targetAmplitude = 0.0

        pstates = enum('CONSTANT','ATTACK','DECAY', 'OFF')
        
        pulseState = pstates.OFF
        
        sndLock = threading.Condition()
        
        sndBuff1 = soundBuffer(bufferLength, sndLock)
        sndBuff1.start()
        
        time.sleep(1.0)     # wait for sound threads to start
        
        startTime = time.clock()
        
        
        while(not self._stop.isSet() ):
        
            if(self.pulsing == True):
                pulseState = pstates.ATTACK
                amplitude = 0
                targetAmplitude = self.outAmplitude
                rampRate = targetAmplitude / (rate * attackTime)
            else:
                pulseState = pstates.CONSTANT
                amplitude = self.outAmplitude
        
            constEndTime = pulseTime + pulsePeriod
         
            for sample in range(bufferLength):
                pulseTime = sample * period     # real time into this pulse

                if(pulseState == pstates.ATTACK):
                    amplitude = amplitude + rampRate
                    if(amplitude > targetAmplitude):
                        pulseState = pstates.CONSTANT
                        amplitude = targetAmplitude
                        constEndTime = pulseTime + pulsePeriod
                elif(pulseState == pstates.CONSTANT):
                    if(pulseTime > constEndTime):
                        pulseState = pstates.DECAY
                        rampRate = amplitude /  (rate * decayTime)
                        targetAmplitude = 0
                elif(pulseState == pstates.DECAY):
                    amplitude = amplitude - rampRate
                    if(amplitude < 0):
                        amplitude = 0
                        pulseState = pstates.OFF
                
                deltaangle = period * self.frequency * 2.0 * math.pi
                angle = angle + deltaangle
                
                while angle > math.pi:
                    angle = angle - math.pi
                wave[sample] = math.sin(angle) * amplitude * self.outAmplitude

            try:
                sndBuff1.buffer.put(wave, True, bufferTime * 2)
            except:
                continue
                
        
        sndBuff1.stop()
        
        sndBuff1.join(1.0)
        
        print("sound generator thread end")


class vario():
    def __init__(self):
        self.soundGen = varioSoundGen()
        self.soundGen.start()
        
        self.deadband   = 50.0
        self.maxRate    = 300.0
        self.minRate    = -300.0
        self.maxRisingFreq  = 800.0
        self.minRisingFreq  = 300.0
        self.maxFallingFreq = 250.0
        self.minFallingFreq = 150.0
        
        self.volume = 0.5;

    def __del__(self):
        self.soundGen.stop()
        
    def setRate(self, rate):
        if(rate > self.deadband):
            self.soundGen.setPulsing(True)
            self.soundGen.setFrequency( (rate-self.deadband * (self.maxRisingFreq - self.minFallingFreq) / (self.maxRate - self.deadband)) + self.minRisingFreq)
            self.soundGen.setAmplitude(self.volume)
            
        elif(rate < -self.deadband):
            if(rate < self.minRate):
                self.soundGen.setFrequency(self.minFallingFreq)
                self.soundGen.setPulsing(True)
                self.soundGen.setAmplitude(self.volume)
            else:
                self.soundGen.setPulsing(False)
                freq = rate+self.deadband
                freq = freq / (self.minRate + self.deadband)
                freq = freq * (self.minFallingFreq - self.maxFallingFreq)
                freq = freq + self.maxFallingFreq
                self.soundGen.setFrequency( freq )
                self.soundGen.setAmplitude(self.volume)
        else:
            self.soundGen.setAmplitude(0.0)
    
    
    def setVolume(self, volume):
        self.volume = volume;
           